pub fn check_id_card(id_card_no: &str) -> String {
    if id_card_no.len() == 15 {
        process_card15(id_card_no)
    } else if id_card_no.len() == 18 {
        process_card18(id_card_no)
    } else {
        "身份证号码错误".to_owned()
    }
}

// 处理15位身份证
fn process_card15(id_card_no: &str) -> String {
    let id_card: Result<IdCard15<'_>, String> = IdCard15::new(id_card_no);
    match id_card {
        Ok(card) => {
            format!(
                "身份证号码正确,{},{},{}",
                Into::<&'static str>::into(card.sex()),
                card.birth_string(),
                card.province_info
            )
        }
        Err(e) => e,
    }
}

// 处理18位身份证
fn process_card18(id_card_no: &str) -> String {
    let id_card = IdCard18::new(id_card_no);
    match id_card {
        Ok(card) => {
            format!(
                "身份证号码正确,{},{},{}",
                Into::<&'static str>::into(card.sex()),
                card.birth_string(),
                card.search().expect("从数据文件中找不到地址")
            )
        }
        Err(e) => e,
    }
}

// ---------------------------------------------------------------------------------------------------
// 15位身份证号
#[derive(Debug, Clone)]
struct IdCard15<'a> {
    province_code: &'a str,
    birth: &'a str,
    province_info: String, // 最后生成区域信息
    sex: Sex,
}

impl<'a> IdCard15<'a> {
    fn new(id_card_no: &'a str) -> Result<Self, String> {
        if id_card_no.len() != id_card_no.chars().count()   // 存在非ascii码
            || (&id_card_no[0..15]).chars().any(|c| !c.is_numeric())
        // 15位非数字
        {
            return Err(String::from("身份证号码错误"));
        }

        let mut ret = Self {
            province_code: &id_card_no[0..6],
            birth: &id_card_no[6..12],
            province_info: String::new(),
            sex: Sex::Boy,
        };

        if !ret.verify(id_card_no) {
            return Err(String::from("身份证号码错误"));
        }

        Ok(ret)
    }

    // 校验号码
    fn verify(&mut self, id_card_no: &'a str) -> bool {
        // 搜索区域
        if let Ok(pro_info) = search(&self.province_code) {
            self.province_info = pro_info;
        } else {
            return false;
        }
        // 验证出生年月
        let year = (&self.birth[0..2]).parse::<u32>().unwrap();
        let month = (&self.birth[2..4]).parse::<u32>().unwrap();
        let day = (&self.birth[4..6]).parse::<u32>().unwrap();
        if !verity_year_month_day(1900 + year, month, day) {
            return false;
        }

        // 设置性别
        let sex_num = (&id_card_no[14..15]).parse::<u32>().unwrap();
        if sex_num % 2 != 0 {
            self.sex = Sex::Boy;
        } else {
            self.sex = Sex::Girl;
        }
        true
    }

    #[inline(always)]
    fn sex(&self) -> Sex {
        self.sex
    }

    // 生成年月日
    #[inline(always)]
    fn birth_string(&self) -> String {
        format!(
            "19{}年{}月{}日",
            &self.birth[0..2],
            &self.birth[2..4],
            &self.birth[4..6]
        )
    }
}

// ---------------------------------------------------------------------------------------------------
// 18位身份证
#[derive(Debug, Clone, Copy)]
struct IdCard18<'a> {
    province: &'a str,
    birth: &'a str,
    sex: Sex,
}

impl<'a> IdCard18<'a> {
    const FACTOR: [u32; 17] = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
    fn new(id_card_no: &'a str) -> Result<Self, String> {
        if id_card_no.len() != id_card_no.chars().count()   // 存在非ascii码
            || (&id_card_no[0..17]).chars().any(|c| !c.is_numeric())
        // 前17位非数字
        {
            return Err(String::from("身份证号码错误"));
        }
        if !Self::verify(id_card_no) {
            return Err(String::from("身份证号码错误"));
        }

        Ok(Self {
            province: &id_card_no[0..6],
            birth: &id_card_no[6..14],
            sex: Self::get_sex(&id_card_no[16..17]),
        })
    }

    // 校验号码
    fn verify(id_card_no: &str) -> bool {
        let mut tmp = [0];
        let nums = &id_card_no[0..17]
            .chars()
            .map(|c| {
                c.encode_utf8(&mut tmp)
                    .parse::<u32>()
                    .expect("must be right")
            })
            .collect::<Vec<_>>();
        let mut sum = 0;
        for i in 0..17 {
            sum += nums[i] * Self::FACTOR[i]
        }

        let a = 12. - sum as f32;
        let code = a - 11. * (a / 11.).floor();
        if (code as u32) < 10 {
            &code.to_string() == &id_card_no[17..18]
        } else {
            "X" == &id_card_no[17..18]
        }
    }

    #[inline(always)]
    fn sex(&self) -> Sex {
        self.sex
    }

    #[inline(always)]
    fn get_sex(sex_str: &str) -> Sex {
        if sex_str.parse::<u8>().expect("must be right") % 2 != 0 {
            Sex::Boy
        } else {
            Sex::Girl
        }
    }

    // 在文件中搜索地区
    fn search(&self) -> Result<String, String> {
        use std::io::{BufRead, BufReader};
        let file = std::fs::File::open("./province.txt").map_err(|e| e.to_string())?;
        let reader = BufReader::new(file);
        for line in reader.lines() {
            let line = line.map_err(|e| e.to_string())?;
            let mut arrs = line.split(" ");
            if let Some(code) = arrs.next() {
                if self.province == code {
                    // 找到了
                    let info = arrs.collect::<Vec<_>>();
                    if info.len() != 3 {
                        return Err(String::from("文件格式错误"));
                    }
                    if info[2] == "(直辖市)" {
                        return Ok(format!("{}-市辖区-{}", info[0], info[1]));
                    } else {
                        return Ok(format!("{}-{}-{}", info[0], info[1], info[2]));
                    }
                }
            } else {
                return Err(String::from("文件格式错误"));
            }
        }
        Err("地区不存在".to_string())
    }

    // 生成年月日
    #[inline(always)]
    fn birth_string(&self) -> String {
        format!(
            "{}年{}月{}日",
            &self.birth[0..4],
            &self.birth[4..6],
            &self.birth[6..8]
        )
    }
}

// ---------------------------------------------------------------------------------------------------
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
enum Sex {
    Girl,
    Boy,
}

impl Into<&'static str> for Sex {
    fn into(self) -> &'static str {
        match self {
            Sex::Girl => "女",
            Sex::Boy => "男",
        }
    }
}

// 从文件中搜索城市
fn search(code_str: &str) -> Result<String, String> {
    use std::io::{BufRead, BufReader};
    let file = std::fs::File::open("./province.txt").map_err(|e| e.to_string())?;
    let reader = BufReader::new(file);
    for line in reader.lines() {
        let line = line.map_err(|e| e.to_string())?;
        let mut arrs = line.split(" ");
        if let Some(code) = arrs.next() {
            if code_str == code {
                // 找到了
                let info = arrs.collect::<Vec<_>>();
                if info.len() != 3 {
                    return Err(String::from("文件格式错误"));
                }
                if info[2] == "(直辖市)" {
                    return Ok(format!("{}-市辖区-{}", info[0], info[1]));
                } else {
                    return Ok(format!("{}-{}-{}", info[0], info[1], info[2]));
                }
            }
        } else {
            return Err(String::from("文件格式错误"));
        }
    }
    Err("地区不存在".to_string())
}

// 判断是否是闰年
fn is_leap_year(year: u32) -> bool {
    if year % 400 == 0 {
        return true;
    }

    if year % 4 == 0 && year % 100 != 0 {
        return true;
    }
    false
}

// 测试年月日是否正确
fn verity_year_month_day(year: u32, month: u32, day: u32) -> bool {
    if !(1u32..=12).contains(&month) {
        return false;
    }

    let is_leap = is_leap_year(year);
    let max = match month {
        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
        2 => {
            if is_leap {
                29
            } else {
                28
            }
        }
        4 | 6 | 9 | 11 => 30,
        _ => unreachable!(),
    };

    if !(1..=max).contains(&day) {
        return false;
    }

    true
}
